#include <stdio.h>
#include <string.h>
#include <time.h>


#include "adComputer.h"
#include "ParamManager.h"
#include "Exec.h"
#include "Wmi.h"
#include "common.h"

#ifdef _WIN32
#ifdef _DEBUG
	#define _CRTDBG_MAP_ALLOC
	#include <stdlib.h>
	#include <crtdbg.h>
	#undef THIS_FILE
	static char THIS_FILE[] = __FILE__;
	#define new       new( _NORMAL_BLOCK, __FILE__, __LINE__)
	#define malloc(s) _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__)
#endif
#endif

// Constructor
adComputer::adComputer(HANDLE mutex, int id, char *netbios, ParamManager *detail) {
	// default init
	strcpy(this->netbios, netbios);
	strcpy(this->dn_curr, "");
	this->id = id;

	this->detail = detail;
}

adComputer::~adComputer() {
	/* nothing to do */
}

// dsquery to obtain the dn_curr
bool adComputer::updateDN() {
	char cmd[DEFAULT_BUFLEN];
	char *buffer;

	// if already set - don't check
	if(strcmp(this->dn_curr, "")) {
		return true;
	}

	// lookup current dn location
	sprintf(cmd, "dsquery computer \"%s\" -name \"%s\"",
			this->detail->get("start_node"), this->netbios);
	
	Exec proc(cmd);
	// process output
	if(proc.isOutput()) {
		// get the next line of output
		while(buffer = proc.getNext()) {
			// skip blank lines
			if(strlen(buffer) == 0) {
				continue;
			}

			// chop off the last "
			buffer[strlen(buffer) - 1] = '\0';
			// chop off the first "
			strcpy(buffer, buffer+1);

			// work with this first result
			strcpy(this->dn_curr, buffer);
//			printf("[updateDN] remembering dn_curr: '%s'\n", this->dn_curr);

			return true;
		}
	}

//	printf("adComputer construct: '%s'\r\n", this->dn_curr);
	return false;
}

// Returns true if object exists, false if not
bool adComputer::isValid() {
	// checks for a DN entry, results in a duplicate check if it failed previously,
	// this isn't such a big deal because the number of objects deleted during this
	// process is very low
	this->updateDN();

	// if not set - then !isValid
	if(!strcmp(this->dn_curr, "")) {
		return false;
	}

	return true;
}

// Returns a current status
bool adComputer::isEnabled() {
	char cmd[DEFAULT_BUFLEN];
	char *buffer;

	// Although, possibly misleading, if there is no AD object, we return false
	if(!this->isValid()) {
		return false;
	}

	sprintf(cmd, "dsget computer -disabled -L \"%s\"", this->dn_curr);
//	printf("isEnabled cmd = '%s'\r\n", cmd);
	Exec proc(cmd);
	// process output
	if(proc.isOutput()) {
		// get the next line of output
		while(buffer = proc.getNext()) {
			// skip blank lines
			if(strlen(buffer) == 0) {
				continue;
			}

			// process line of output
			if(strstr(buffer, "disabled: no")) {
				return true;
			}
			else if(strstr(buffer, "disabled: yes")) {
				return false;
			}
		}
	}

	// this point is only reached if dsget failed
	return NULL;
}

// if the object is stale over specified num of days
// if there is no AD object, then return false
bool adComputer::isStale(int days) {
	char cmd[DEFAULT_BUFLEN];
	char *buffer;

	sprintf(cmd, "dsquery computer \"%s\" -name \"%s\" -stalepwd %d",
			this->detail->get("start_node"), this->netbios, days);
//	printf("isState: '%s'\n", cmd);

	Exec proc(cmd);
	// process output
	if(proc.isOutput()) {
		// get the next line of output
		while(buffer = proc.getNext()) {
			// skip blank lines
			if(strlen(buffer) == 0) {
				continue;
			}

			// object is stale and we have the current dn

			// chop off first and last quotes
			buffer ++;
			buffer[strlen(buffer) - 1] = '\0';

			// remember this dn_curr if it isn't already set
			if(this->dn_curr[0] == '\0') {
				strcpy(this->dn_curr, buffer);
			}

			return true;
		}
	}

	// If no AD object exists, return true
	if(!this->isValid()) {
		return true;
	}

	return false;
}

// if the object pings
bool adComputer::isPing() {
	// Initialize WMI for use with our ping action
/*	Wmi wmi;
	if(wmi.initialize()) {
		if(wmi.connect()) {
			if(wmi.setSecurity()) {
				return (wmi.ping(netbios) == 1);
			}
		}
	}
*/
	char cmd[DEFAULT_BUFLEN];
	char *buffer;

	sprintf(cmd, "ping -n 1 %s", netbios);
	Exec proc(cmd);

	if(proc.isOutput()) {
		// get the next line of output
		while(buffer = proc.getNext()) {
			// skip blank lines
			if(strlen(buffer) == 0) {
				continue;
			}

//			printf("buffer: '%s'\n", buffer);

			// string to check for if successful
			if(strstr(buffer, "time=")) {
				return true;
			}
			// string to check for if successful
			if(strstr(buffer, "time<")) {
				return true;
			}
		}
	}

	return false;
}

// if the object matches one of the provided exceptions
Param* adComputer::isException(ParamManager *exceptions) {
	Param *slider = exceptions->head;
	regex_t start_state;

	// loop through each exception
	while(slider != NULL) {
		// form regex pattern from slider->data
		if(regcomp(&start_state, slider->data, REG_EXTENDED)) {
			fprintf(stderr, "bad pattern: '%s'\n", slider->data);
			regfree(&start_state);	// was this never allocated?
			return NULL;
		}

//		printf("[isException] '%s' ? '%s'\n", slider->data, this->netbios);

		// test for exception
		int status = regexec(&start_state, this->netbios, 0, NULL, 0);
		regfree(&start_state);	// free up resource

		if(status == 0) {
			// match
			return slider;
		}

		slider = slider->next;
	}

	return NULL;
}

// move the object from its current location to specified location
bool adComputer::moveTo(HANDLE mutex, char *dest_dn) {
	char cmd[DEFAULT_BUFLEN];
	char *buffer;

	// If no AD object exists, return false (also obtain current dn if none is set)
	if(!this->isValid()) {
		return false;
	}

	sprintf(cmd, "dsmove \"%s\" -newparent \"%s\"", this->dn_curr, dest_dn);
	printf("Action: move_to '%s'\n", cmd);

	Exec proc(cmd);
	// process output
	if(proc.isOutput()) {
		// get the next line of output
		while(buffer = proc.getNext()) {
			// skip blank lines
			if(strlen(buffer) == 0) {
				continue;
			}

			if(strstr(buffer, "smove succeeded") != NULL) {  // ignore first char so '0' isn't returned
				// update dn_curr in this object
				strcpy(this->dn_curr, "");
				this->updateDN();

				//sprintf(buffer, "netbios=%s|cmd=movedto|dest_ou=%s", this->netbios, this->dn_curr);
				sprintf(buffer, "%s has been moved to '%s'\n", this->netbios, this->dn_curr);
				log(mutex, buffer);

				return true;
			}
		}
	}

	return false;
}

// disable this object
bool adComputer::disable(HANDLE mutex) {
	char cmd[DEFAULT_BUFLEN];
	char *buffer;

	// If no AD object exists, return false (also obtain current dn if none is set)
	if(!this->isValid()) {
		return false;
	}

	sprintf(cmd, "dsmod computer \"%s\" -disabled yes", this->dn_curr);

	Exec proc(cmd);
	// process output
	if(proc.isOutput()) {
		// get the next line of output
		while(buffer = proc.getNext()) {
			// skip blank lines
			if(strlen(buffer) == 0) {
				continue;
			}

			if(strstr(buffer, "smod succeeded")) {  // ignore first char so '0' isn't returned
				//sprintf(buffer, "netbios=%s|cmd=disabled", this->netbios);
				sprintf(buffer, "%s has been disabled\n", this->netbios);
				log(mutex, buffer);
				return true;
			}
		}
	}

	return false;
}

// enable this object
bool adComputer::enable(HANDLE mutex) {
	char cmd[DEFAULT_BUFLEN];
	char *buffer;

	// If no AD object exists, return false (also obtain current dn if none is set)
	if(!this->isValid()) {
		return false;
	}

	sprintf(cmd, "dsmod computer \"%s\" -disabled no", this->dn_curr);

	Exec proc(cmd);
	// process output
	if(proc.isOutput()) {
		// get the next line of output
		while(buffer = proc.getNext()) {
			// skip blank lines
			if(strlen(buffer) == 0) {
				continue;
			}

			if(strstr(buffer, "smod succeeded")) {  // ignore first char so '0' isn't returned
				//sprintf(buffer, "netbios=%s|cmd=enabled", this->netbios);
				sprintf(buffer, "%s has been enabled\n", this->netbios);
				log(mutex, buffer);
				return true;
			}
		}
	}

	return false;
}

// sets a value in the database
bool adComputer::setBool(HANDLE mutex, char *data, bool value) {
	MYSQL_RES *rUpdate;
	char query[DEFAULT_BUFLEN];

	// Increase the track_id
	sprintf(query, "UPDATE n_adc_%s SET %s=%d WHERE id = %d",
			this->detail->get("instance"), data, value, this->id);

	// thread-safe mysql call
	rUpdate = db->threadsafe_query(mutex, query);
	// free up the result form the mysql query
	if(rUpdate) {
		// free up the result form the mysql query
		mysql_free_result(rUpdate);
	}

	char buffer[DEFAULT_BUFLEN];
	//sprintf(buffer, "netbios=%s|cmd=setBool|data=%s|value=%d",
	sprintf(buffer, "%s, setting %s=%d\n",
				this->netbios, data, value);
	log(mutex, buffer);

	// always return true for now
	return true;
}

bool adComputer::setString(HANDLE mutex, char *data, char *value) {
	MYSQL_RES *rUpdate = NULL;
	char query[DEFAULT_BUFLEN];

	// Increase the track_id
	sprintf(query, "UPDATE n_adc_%s SET %s=\"%s\" WHERE id = %d",
			this->detail->get("instance"), data, value, this->id);

	// thread-safe mysql call
	rUpdate = db->threadsafe_query(mutex, query);
	if(rUpdate) {
		// free up the result form the mysql query
		mysql_free_result(rUpdate);
	}

	char buffer[DEFAULT_BUFLEN];
//	sprintf(buffer, "netbios=%s|cmd=setString|data=%s|value=%s",
	sprintf(buffer, "%s, setting %s='%s'\n",
				this->netbios, data, value);
	// avoid logging 'track_id'
	if(strcmp(data, "track_id")) {
		log(mutex, buffer);
	}

	// always return true for now
	return true;
}

// only works if status=active; indicates obj has moved out of the stale ou
bool adComputer::wasMoved(HANDLE mutex) {
	MYSQL_RES *rSelect;
	MYSQL_ROW row;
	char query[DEFAULT_BUFLEN];

	// Only proceed if there is an existing AD Object, return false otherwise
	if(!this->isValid()) {
		return false;
	}

	// Increase the track_id
	sprintf(query, "SELECT dn_curr FROM n_adc_%s WHERE id = %d",
			this->detail->get("instance"), this->id);

	// thread-safe mysql call
	rSelect = db->threadsafe_query(mutex, query);

	bool moved = false;

	if(rSelect) {
		if(row = mysql_fetch_row(rSelect)) { 
			// if the stored dn_curr is different from the current dn_curr
//			printf("wasMoved: '%s' '%s'\n", this->dn_curr, row[0]);
			if(strcmp(this->dn_curr, row[0])) {
				// then the object has moved
				moved = true;
			}
		}

		// free up the result form the mysql query
		mysql_free_result(rSelect);
	}

	// always return true for now
	return moved;
}

// Logs the message to the database
void adComputer::log(HANDLE mutex, char *msg) {
	MYSQL_RES *rUpdate;
	char query[DEFAULT_BUFLEN];
	char dt[DEFAULT_BUFLEN];

	// obtain a timestamp
	time_t rawtime;
	struct tm *timeinfo;

	time(&rawtime);
	timeinfo = localtime(&rawtime);
	strftime(dt, DEFAULT_BUFLEN, "%Y-%m-%d %H:%M", timeinfo);

	// Increase the track_id
	sprintf(query, "INSERT INTO n_adc_%s_log (dt, msg, track_id) VALUES (\"%s\",\"%s\",\"%s\")",
			this->detail->get("instance"),
			dt,
			msg,
			this->detail->get("track_id")
			);
//	printf("query is '%s'\n", query);

	// thread-safe mysql call
	rUpdate = db->threadsafe_query(mutex, query);
	if(rUpdate) {
		// free up the result form the mysql query
		mysql_free_result(rUpdate);
	}
}